package com.hc.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.hc.cache.RedisCacheManager;
import com.hc.util.HcUtil;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro框架相关的配置类
 */
@Configuration
public class ShiroConfig {

    /**
     * 1.创建realm对象，使用自定义类
     *
     * @return
     */
    @Bean(name = "userRealm")
    public UserRealm getUserRealm() { //将自己的验证方式加入容器
        UserRealm userRealm = new UserRealm();

        //修改凭证检验匹配器
        // 设置用于匹配密码的加密算法为md5
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("md5");
        // 设置加密次数
        credentialsMatcher.setHashIterations(HcUtil.SHIRO_DECODE_ITERATIONS);
        //启用十六进制存储
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        userRealm.setCredentialsMatcher(credentialsMatcher);

        //因为默认的SessionManager内部默认采用了内存方式存储Session相关信息，不适合企业生产应用（特别并发认证用户较多的系统），在实际应用中使用Redis管理Shiro默认的Session（SessionManager）是必要的
        //开启缓存管理，以后每次请求都会先走Cache
        userRealm.setCacheManager(new RedisCacheManager());
        //开启全局缓存
        userRealm.setCachingEnabled(true);
        //认证认证缓存
        userRealm.setAuthenticationCachingEnabled(true);
        userRealm.setAuthenticationCacheName("authenticationCache");
        //开启授权缓存
        userRealm.setAuthorizationCachingEnabled(true);
        userRealm.setAuthorizationCacheName("authorizationCache");

        return userRealm;
    }

    /**
     * 2.创建DefaultWebSecurityManager
     * 创建安全管理器：这个负责拦截所有请求
     *
     * @param userRealm
     * @return
     */
    @Bean(name = "dwSecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm,
                                                                  @Qualifier("rememberMeManager") RememberMeManager rememberMeManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //给安全管理器设置realm
        securityManager.setRealm(userRealm);
        //记住我功能
        securityManager.setRememberMeManager(rememberMeManager);
        return securityManager;
    }

    /**
     * 3.创建ShiroFilterFactoryBean
     * 创建ShiroFilter
     *
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("dwSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给ShiroFilter设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //设置默认的认证界面（登录页面）
        shiroFilterFactoryBean.setLoginUrl("/login");
        //设置未授权页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");


        Map<String, String> map = new LinkedHashMap<>();
        //配置系统的公共资源（公共资源需要写在受限资源前面）
        map.put("/user/login", "anon"); //打开登录页面不受限
        map.put("/regist", "anon"); //打开注册页面不受限
        map.put("/user/regist", "anon"); //注册的路径不受限
        map.put("/user/getVerifyCode", "anon"); //注册的路径不受限

        //配置系统的受限资源（必须认证了才能访问）
        map.put("/user/add", "authc");
        map.put("/user/update", "authc");
        //上面两个可以使用通配符  map.put("/user/*","authc");
        // /**表示除了setLoginUrl以外的所有
        map.put("/**", "authc");

        //授权，若当前登录用户未授权则会跳转到授权页面
        map.put("/user/add", "perms[user:add]");
        map.put("/user/update", "perms[user:update]");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

    /**
     * 为 Spring-Bean 开启对 Shiro 注解的支持
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
     *
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    /**
     * 开启aop注解支持
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("dwSecurityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    //Shiro整合thymeleaf
    @Bean
    public ShiroDialect getShiroDialect() {
        ShiroDialect shiroDialect = new ShiroDialect();
        return shiroDialect;
    }

    //记住我
    @Bean(name = "rememberMeManager")
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        //这个地方有点坑，不是所有的base64编码都可以用，长度过大过小都不行，没搞明白，官网给出的要么0x开头十六进制，要么base64
        cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
        return cookieRememberMeManager;
    }

    //cookie管理
    @Bean
    public SimpleCookie rememberMeCookie() {
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(1 * 60 * 60);
        return cookie;
    }

}
